/*
-- IOTA Crypto Core
--
-- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
-- discord: pmaxuw#8292
-- https://gitlab.com/iccfpga-rv
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
-- 
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
-- 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
*/

#include <assert.h>
#include <fpga/conversion_fpga.h>
#include <fpga/fpga.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#include "main.h"

#include "debugprintf.h"

#include "fpga/test.h"
#include "fpga/sha3.h"
#include "fpga/troika.h"
#include "fpga/pow.h"
#include "fpga/sha512.h"
#include "sha512/sha512.h"
#include "micro-os-plus/diag/trace.h"

#include "iota/transaction.h"

//#define ENABLE_FPGA_TESTS

#ifdef ENABLE_FPGA_TESTS

extern Transaction txs[MAX_NUM_TRANSACTIONS];


extern buffer_t buffer;

extern uint32_t systick;

namespace Tests {
	namespace {
		const char* troika_result = "120220121212020211012202221"
				"121212221211022112222201212"
				"001221011202211020020000200"
				"100012021222011211111202210"
				"102202211120102211222000002"
				"210202121001221010022001101"
				"021010000021221011220002100"
				"012221020010112001222020112"
				"100211020221121101101102212";

		const char* test_tx_nonce = "ICCFPGA9B9999999ONNVTVPMMMM"; // parallel 4
		const char* test_curl_result = "QDIAOXXKUIYGZHLOUG9HZRRZOYXEEBLJFMOIUYCEYFSMTIKMJPJSUQMOUUCBIQXMATMYURUTFJWXZ9999"; // parallel 4
	}

	bool MMTEXT troika() {
		uint8_t* input = &buffer.scratch[0];
		uint8_t* result = &buffer.scratch[243];

		memset(input, 0, 243);
		input[0] = 1;
		input[242] = 2;

		TroikaFPGA troika;
		assert(troika.memoryLoopTest());

		troika.doTroika(result, 243, input, 243);

		for (int i = 0; i < 243; i++) {
			if (result[i] != troika_result[i] - 48) {
//				trace_printf("error %d: %d %d\n", i, result[i], troika_result[i]);
				return false;
			}
		}

		debugPrintf("%s PASS\n", __FUNCTION__);
		return true;
	}

	// compare hardware accelerated keccak with software-implementation
	bool MMTEXT keccak384() {
		uint32_t hash[12] = { 0 };
		uint32_t hashcmp[12] = { 0 };

		uint8_t* testData = &buffer.scratch[0];
		for (int i=0;i<288;i++) {
			testData[i] = rand() % 0xff;
		}

		Keccak384FPGA sha;
		sha.init();
		sha.update(testData, 288);
		sha.final((unsigned char*) hash);

		Keccak384 shacmp;
		shacmp.init();
		shacmp.update(testData, 288);
		shacmp.final((unsigned char*) hashcmp);

		for (int i = 0; i < 12; i++) {
			if (hash[i] != hashcmp[i])
				return false;
		}
		debugPrintf("%s PASS\n", __FUNCTION__);
		return true;
	}

	bool MMTEXT doPoW() {
		char nonce[28]= {0};
		Pow pow;

		memset(&buffer.scratch[0], '9', sizeof(TX_t));

		pow.init();
		pow.doPoW((const char*) &buffer.scratch[0], nonce, 14);

//		debugPrintf("%s\n", nonce);

		for (int i=0;i<27;i++) {
			if (nonce[i] != test_tx_nonce[i])
				return false;
	//		trace_printf("%c", nonce[i]);
		}
	//	trace_printf("\n");
		debugPrintf("%s PASS\n", __FUNCTION__);
		return true;
	}

	bool MMTEXT bigintToTritsRandom() {
		uint8_t* bytes = (uint8_t*) &buffer.scratch[0];
		uint8_t* bytes2 = (uint8_t*) &buffer.scratch[48];
		trit_t* tritsFPGA = (trit_t*) &buffer.scratch[96];
		trit_t* trits = (trit_t*) &buffer.scratch[339];

		ConversionFPGA convFPGA;
		Conversion conv;

		uint32_t s = systick;
		for (int i=0;i<1000;i++) {
			uint32_t* rptr = (uint32_t*) bytes;
			for (uint32_t j=0;j<48/sizeof(uint32_t);j++) {
				*rptr++ = rand();
			}
			memcpy(bytes2, bytes, 48);
			convFPGA.bytes_to_trits(bytes, tritsFPGA);
			conv.bytes_to_trits(bytes2, trits);

			for (int j=0;j<243;j++) {
				if (tritsFPGA[j] != trits[j]) {
					debugPrintf("error at %d: %d %d\n", j, tritsFPGA[j], trits[j]);
					return false;
				}

			}
		}
		debugPrintf("%s PASS %d\n", __FUNCTION__, systick - s);

		return true;
	}

	bool MMTEXT tritsToBigintRandom() {
		uint8_t* bytes = (uint8_t*) &buffer.scratch[0];
		uint8_t* bytesFPGA = (uint8_t*) &buffer.scratch[48];
		trit_t* trits = (trit_t*) &buffer.scratch[96];

		ConversionFPGA convFPGA;
		Conversion conv;

		uint32_t s = systick;
		for (int i=0;i<1000;i++) {
			for (uint32_t j=0;j<243;j++) {
				trits[j] = (int8_t) (rand() % 4) - 1;
				if (trits[j] == 2)	// %3
					trits[j] = 0;
			}
			conv.trits_to_bytes(trits, bytes);
			convFPGA.trits_to_bytes(trits, bytesFPGA);
			for (int j=0;j<48;j++) {
				if (bytes[j] != bytesFPGA[j])
					return false;
			}
		}
		debugPrintf("%s PASS %d\n", __FUNCTION__, systick - s);
		return true;
	}

	bool MMTEXT trytesToBigintRandom() {
		uint8_t* bytes = (uint8_t*) &buffer.scratch[0];
		uint8_t* bytesFPGA = (uint8_t*) &buffer.scratch[48];
		tryte_t* trytes = (tryte_t*) &buffer.scratch[96];

		ConversionFPGA convFPGA;
		Conversion conv;

		uint32_t s = systick;
		for (int i=0;i<1000;i++) {
			for (uint32_t j=0;j<81;j++) {
				trytes[j] = (int8_t) (rand() % 27)-13;
			}
			conv.trytes_to_bytes(trytes, bytes);
			convFPGA.trytes_to_bytes(trytes, bytesFPGA);
			for (int j=0;j<48;j++) {
				if (bytes[j] != bytesFPGA[j])
					return false;
			}
		}
		debugPrintf("%s PASS %d\n", __FUNCTION__, systick - s);
		return true;
	}

	bool MMTEXT bigintToTrytesRandom() {
		uint8_t* bytes = (uint8_t*) &buffer.scratch[0];
		tryte_t* trytes = (tryte_t*) &buffer.scratch[48];
		tryte_t* trytesFPGA = (tryte_t*) &buffer.scratch[129];

		ConversionFPGA convFPGA;
		Conversion conv;

		uint32_t s = systick;
		for (int i=0;i<1000;i++) {
			uint32_t* rptr = (uint32_t*) bytes;
			for (uint32_t j=0;j<48/sizeof(uint32_t);j++) {
				*rptr++ = rand();
			}
			conv.bytes_to_trytes(bytes, trytes);
			convFPGA.bytes_to_trytes(bytes, trytesFPGA);
			for (int j=0;j<81;j++) {
				if (trytes[j] != trytesFPGA[j])
					return false;
			}
		}
		debugPrintf("%s PASS %d\n", __FUNCTION__, systick - s);
		return true;
	}

	bool MMTEXT curl() {
		char hash[82]={0};

		memset(&buffer.scratch[0], '9', sizeof(TX_t));
		memcpy(&buffer.scratch[2673-27], test_tx_nonce, 27);

		Pow pow;
		pow.init();
		pow.powCurlHash((const char*) &buffer.scratch[0], hash);

//		debugPrintf("%s\n", hash);


	 	for (int i=0;i<81;i++) {
			if (hash[i] != test_curl_result[i])
				return false;
		}
		debugPrintf("%s PASS\n", __FUNCTION__);
		return true;
	}

	bool MMTEXT sha512() {
//		const char* msg="abc";
		uint8_t* msg = (uint8_t*) &buffer.scratch[0];
		for (int i=0;i<288;i++) {
			msg[i] = rand() % 0xff;
		}

		uint8_t resultC[64]={0};
		uint8_t resultFPGA[64]={0};

		SHA512FPGA sha512FPGA;
		sha512FPGA.sha512((uint8_t*) msg, 288, resultFPGA);

		SHA512 sha512;
		sha512.sha512((uint8_t*) msg, 288, resultC);


		for (int i=0;i<64;i++) {
			if (resultFPGA[i] != resultC[i]) {
				return false;
			}
		}
		debugPrintf("%s PASS\n", __FUNCTION__);
		return true;
	}

	bool MMTEXT all() {
		assert(sha512());
		assert(doPoW());
		assert(troika());
		assert(keccak384());
		assert(curl());
		assert(bigintToTritsRandom());
		assert(tritsToBigintRandom());
		assert(trytesToBigintRandom());
		assert(bigintToTrytesRandom());
		return true;
	}
}
#endif
